package mips;

import temp.TempList;
import assem.Instr;
import assem.InstrList;
import assem.OPER;

public class Codegen {
	Frame frame;

	public Codegen(Frame f) {frame = f;}

	private InstrList ilist = null, tail = null;

	private void emit(Instr inst) {
		if (tail != null)
			tail = tail.tail = new InstrList(inst, null);
		else
			tail = ilist = new InstrList(inst, null);
	}

	String getImmOP(int opCode) {
		return "addi";
	}

	String getOP(int opCode) {
		if (opCode == tree.BINOP.PLUS) return "add";
		if (opCode == tree.BINOP.MINUS) return "sub";
		if (opCode == tree.BINOP.MUL) return "mul";
		if (opCode == tree.BINOP.DIV) return "div";
		return null;
	}

	String getBranchOP(int rel) {
		switch (rel) {
		case tree.CJUMP.EQ : return "beq";
		case tree.CJUMP.GE : return "bge";
		case tree.CJUMP.GT : return "bgt";
		case tree.CJUMP.LE : return "ble";
		case tree.CJUMP.LT : return "blt";
		case tree.CJUMP.NE : return "bne";
		case tree.CJUMP.UGE : return "bgeu";
		case tree.CJUMP.UGT : return "bgtu";
		case tree.CJUMP.ULE : return "bleu";
		case tree.CJUMP.ULT : return "bltu";
		default : return null;
		}
	}

	String getBranchZeroOP(int rel, boolean rev) {
		if (!rev)
			switch (rel) {
			case tree.CJUMP.EQ : return "beqz";
			case tree.CJUMP.GE : return "bgez";
			case tree.CJUMP.GT : return "bgtz";
			case tree.CJUMP.LE : return "blez";
			case tree.CJUMP.LT : return "bltz";
			case tree.CJUMP.NE : return "bnez";
			default : return null;
			}
		else
			switch (rel) {
			case tree.CJUMP.EQ : return "beqz";
			case tree.CJUMP.GE : return "blez";
			case tree.CJUMP.GT : return "bltz";
			case tree.CJUMP.LE : return "bgez";
			case tree.CJUMP.LT : return "bgtz";
			case tree.CJUMP.NE : return "bnez";
			default : return null;
			}
	}
	
	temp.Temp munchCALL(tree.CALL c) {
		int acnt = c.argCnt();
		int cnt = 0;
		
		if (acnt > 5) emit(new assem.OPER("\taddi\t'd0, 's0, " + ((5 - acnt) * 4) + "\n", new TempList(frame.spReg, null), new TempList(frame.spReg, null)));
		emit(new assem.MOVE("\tori\t'd0, 's0, 0\n", frame.RV(), munchExp(c.args.head)));
		for (tree.ExpList el = c.args.tail; el != null; el = el.tail) {
			if (cnt < 4)
				emit(new assem.MOVE("\tori\t'd0, 's0, 0\n", frame.argReg.get(cnt), munchExp(el.head)));
			else
				emit(new assem.OPER("\tsw\t's0, " + (4 * (cnt - 4)) + "('s1)\n", null, new TempList(munchExp(el.head), new TempList(frame.spReg, null))));
			cnt ++;
		}
		emit(new assem.OPER("\tjal\t" + ((tree.NAME) c.func).label.toString() + "\n", frame.getCallModifyRegs(), null));
		if (acnt > 5) emit(new assem.OPER("\taddi\t'd0, 's0, " + ((acnt - 5) * 4) +"\n", new TempList(frame.spReg, null), new TempList(frame.spReg, null)));
		return frame.RV();
	}

	void munchMOVE(tree.MOVE s) {
		if (s.dst instanceof tree.MEM) {
			tree.MEM dst = (tree.MEM)s.dst;
			if (dst.exp instanceof tree.BINOP && ((tree.BINOP)dst.exp).binop == tree.BINOP.PLUS) {
				tree.BINOP bo = (tree.BINOP)dst.exp;
				if (bo.right instanceof tree.CONST)
					emit(new OPER("\tsw\t\'s1, " + ((tree.CONST)bo.right).value + "(\'s0)\n", null, new TempList(munchExp(bo.left), new TempList(munchExp(s.src), null))));
				else if (bo.left instanceof tree.CONST)
					emit(new OPER("\tsw\t\'s1, " + ((tree.CONST)bo.left).value + "(\'s0)\n", null, new TempList(munchExp(bo.right), new TempList(munchExp(s.src), null))));
				else
					emit(new OPER("\tsw\t\'s1, 0(\'s0)\n", null, new TempList(munchExp(dst.exp), new TempList(munchExp(s.src), null))));
					
			}
			else if (dst.exp instanceof tree.CONST)
				emit(new OPER("\tsw\t\'s0, " + ((tree.CONST)dst.exp).value + "($zero)\n", null, new TempList(munchExp(s.src), null)));
			else
				emit(new OPER("\tsw\t\'s1, 0(\'s0)\n", null, new TempList(munchExp(dst.exp), new TempList(munchExp(s.src), null))));
		}
		else if (s.dst instanceof tree.TEMP){
			tree.TEMP tmp = (tree.TEMP)s.dst;
			if (s.src instanceof tree.MEM) {
				tree.MEM src = (tree.MEM)s.src;
				if (src.exp instanceof tree.BINOP && ((tree.BINOP)src.exp).binop == tree.BINOP.PLUS) {
					tree.BINOP bo = (tree.BINOP)src.exp;
					if (bo.right instanceof tree.CONST)
						emit(new OPER("\tlw\t\'d0, " + ((tree.CONST)bo.right).value + "(\'s0)\n", new TempList(tmp.temp, null), new TempList(munchExp(bo.left), null)));
					else if (bo.left instanceof tree.CONST)
						emit(new OPER("\tlw\t\'d0, " + ((tree.CONST)bo.left).value + "(\'s0)\n", new TempList(tmp.temp, null), new TempList(munchExp(bo.right), null)));
					else
						emit(new OPER("\tlw\t\'d0, 0('s0)\n", new TempList(tmp.temp, null), new TempList(munchExp(bo), null)));
				}
				else if (src.exp instanceof tree.CONST)
					emit(new OPER("\tlw\t\'d0, " + ((tree.CONST)src.exp).value + "($zero)\n", new TempList(tmp.temp, null), null));
				else
					emit(new OPER("\tlw\t\'d0, 0('s0)\n", new TempList(tmp.temp, null), new TempList(munchExp(src.exp), null)));
			}
			else
				emit(new assem.MOVE("\tori\t\'d0, \'s0, 0\n", tmp.temp, munchExp(s.src)));
		}
	}

	void munchLABEL(tree.LABEL l) {
		emit(new assem.LABEL(l.label.toString() + ":\n", l.label));
	}

	void munchJUMP(tree.JUMP j) {
		emit(new assem.OPER("\tb\t" + j.targets.head.toString() + "\n", null, null, new temp.LabelList(j.targets.head, null)));
	}

	void munchCJUMP(tree.CJUMP c) {
		if ((c.relop < tree.CJUMP.UGE) && (c.right instanceof tree.CONST) && (((tree.CONST)c.right).value == 0))
			emit(new OPER("\t" + getBranchZeroOP(c.relop, false) + "\t\'s0, " + c.iftrue.toString() + "\n", null, new TempList(munchExp(c.left), null), new temp.LabelList(c.iftrue, null)));
		else if ((c.relop < tree.CJUMP.UGE) && (c.left instanceof tree.CONST) && (((tree.CONST)c.left).value == 0))
			emit(new OPER("\t" + getBranchZeroOP(c.relop, true) + "\t\'s0, " + c.iftrue.toString() + "\n", null, new TempList(munchExp(c.right), null), new temp.LabelList(c.iftrue, null)));
		else
			emit(new OPER("\t" + getBranchOP(c.relop) + "\t\'s0, \'s1, " + c.iftrue.toString() + "\n", null, new TempList(munchExp(c.left), new TempList(munchExp(c.right), null)), new temp.LabelList(c.iftrue, null)));
	}

	void munchStm(tree.BasicStm s) {
		if (s instanceof tree.SEQ) {munchStm(((tree.SEQ)s).left); munchStm(((tree.SEQ)s).right);}
		if (s instanceof tree.MOVE) munchMOVE((tree.MOVE)s);
		if (s instanceof tree.LABEL) munchLABEL((tree.LABEL)s);
		if (s instanceof tree.JUMP) munchJUMP((tree.JUMP)s);
		if (s instanceof tree.CJUMP) munchCJUMP((tree.CJUMP)s);
		if (s instanceof tree.EXP) munchExp(((tree.EXP)s).exp);
		
	}

	temp.Temp munchMEM(tree.MEM m) {
		temp.Temp r = new temp.Temp();
		if ((m.exp instanceof tree.BINOP) && ((tree.BINOP)m.exp).right instanceof tree.CONST) {
			tree.CONST c = (tree.CONST)(((tree.BINOP)m.exp).right);
			tree.BasicExp e1 = ((tree.BINOP)m.exp).left;
			emit(new OPER("\tlw\t\'d0, " + c.value + "(\'s0)\n", new TempList(r, null), new TempList(munchExp(e1), null)));
		}
		else if ((m.exp instanceof tree.BINOP) && ((tree.BINOP)m.exp).left instanceof tree.CONST) {
			tree.CONST c = (tree.CONST)(((tree.BINOP)m.exp).left);
			tree.BasicExp e1 = ((tree.BINOP)m.exp).right;
			emit(new OPER("\tlw\t\'d0, " + c.value + "(\'s0)\n", new TempList(r, null), new TempList(munchExp(e1), null)));
		}
		else if (m.exp instanceof tree.CONST)
			emit(new OPER("\tlw\t\'d0, " + ((tree.CONST)m.exp).value + "($zero)\n", new TempList(r, null), null));
		else
			emit(new OPER("\tlw\t\'d0, 0(\'s0)\n", new TempList(r, null), new TempList(munchExp(m.exp), null)));
		return r;
	}

	temp.Temp munchBINOP(tree.BINOP bop) {
		//TODO deal with multiplication here
		temp.Temp r = new temp.Temp();
		if ((bop.binop == tree.BINOP.PLUS) && (bop.left instanceof tree.CONST)) {
			int c = ((tree.CONST)bop.left).value;
			emit(new OPER("\t" + getImmOP(bop.binop) + "\t\'d0, \'s0, " + c + "\n", new TempList(r, null), new TempList(munchExp(bop.right), null)));
		}
		else if ((bop.binop == tree.BINOP.PLUS || bop.binop == tree.BINOP.MINUS) && (bop.right instanceof tree.CONST)) {
			int c = ((tree.CONST)bop.right).value;
			if (bop.binop == tree.BINOP.MINUS) c = -c;
			emit(new OPER("\t" + getImmOP(bop.binop) + "\t\'d0, \'s0, " + c + "\n", new TempList(r, null), new TempList(munchExp(bop.left), null)));
		}
		else
			emit(new OPER("\t" + getOP(bop.binop) + "\t\'d0, \'s0, \'s1\n", new TempList(r, null), new TempList(munchExp(bop.left), new TempList(munchExp(bop.right), null))));
		return r;
	}

	temp.Temp munchCONST(tree.CONST c) {
		temp.Temp r = new temp.Temp();
		emit(new OPER("\tli\t\'d0, " + c.value + "\n", new TempList(r, null), null));
		return r;
	}

	temp.Temp munchTEMP(tree.TEMP t) {
		return t.temp;
	}
	
	temp.Temp munchNAME(tree.NAME n) {
		temp.Temp r = new temp.Temp();
		emit(new OPER("\tla\t'd0, " + n.label.toString()+ '\n', new TempList(r, null), null, null));
		return r;
	}

	temp.Temp munchExp(tree.BasicExp e) {
		if (e instanceof tree.MEM) return munchMEM((tree.MEM)e);
		if (e instanceof tree.BINOP) return munchBINOP((tree.BINOP)e);
		if (e instanceof tree.CONST) return munchCONST((tree.CONST)e);
		if (e instanceof tree.TEMP) return munchTEMP((tree.TEMP)e);
		if (e instanceof tree.CALL) return munchCALL((tree.CALL)e);
		if (e instanceof tree.NAME) return munchNAME((tree.NAME)e);
		return null;
	}

	public InstrList generateCode(tree.StmList s) {
		InstrList l;
		for (tree.StmList i = s; i != null; i = i.tail)
			munchStm(i.head);
		l = ilist;
		ilist = tail = null;
		return l;
	}
}
